package checkers;

import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.Serializable;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Scanner;

/**
 * Creates core logic necessary for a game of checkers game contains a select()
 * method that desired moves are communicated one selection at a time for the
 * source space and each destination. the final destination space should be
 * selected twice if a jump
 * <p>
 */
public class Game implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = -3952742646036050232L;
	public static final int BITSET_LENGTH = 4;
	public static final int BOARD_LENGTH = 8;
	public static final int OCCUPIED_INDEX = 0;
	public static final int PLAYER_INDEX = 1;
	public static final int KING_INDEX = 2;
	public static final int SELECTED_INDEX = 3;
	public static final boolean SLIDE = false;
	public static final boolean JUMP = true;

	long elapsedTime; // NOT constantly updated

	boolean winner;
	boolean player;
	boolean ai;
	Space[][] board;
	LinkedList<Space> selected;
	int difficulty;

	/**
	 * Creates a new Game.
	 * 
	 * @param ai
	 *            If the game is against an AI.
	 * @param difficulty
	 *            If the game is against and AI, the difficulty, otherwise is
	 *            ignored.
	 */
	public Game(boolean ai, int difficulty) {
		createBoard();
		linkSpaces();
		populateBoard();
		this.ai = ai;
		this.difficulty = difficulty;
		player = false;
		winner = false;
		selected = new LinkedList<Space>();
		elapsedTime = 0;

	}

	/**
	 * Creates a game based on an input Game.
	 * 
	 * @param g
	 *            Input Game.
	 */
	public Game(Game g) {
		player = g.player;
		winner = false;
		ai = g.ai;
		createBoard();
		linkSpaces();
		elapsedTime = g.elapsedTime;

		selected = new LinkedList<Space>();

		for (int i = 0; i < BOARD_LENGTH; i++) {
			for (int j = 0; j < BOARD_LENGTH; j++) {
				if (g.board[i][j] != null) {
					board[i][j].setContents(g.board[i][j].getContents());
				}
			}
		}

	}

	private final static int normal = 100; // one checker worth 100
	private final static int king = 200; // a King's worth
	private final static int edge = 10; // effect of king being on the edge

	/**
	 * Scores the board, positive scores are in favor of the AI, negative scores
	 * favor the human player
	 * 
	 * @return The score of the board.
	 */
	public int scoreBoard() {
		int score = 0;
		Space temp = null;
		for (int i = 0; i < BOARD_LENGTH; i++) {
			for (int j = 0; j < BOARD_LENGTH; j++) {
				temp = board[i][j];
				if (temp != null) {
					if (temp.getContents(OCCUPIED_INDEX)
							&& !temp.getContents(PLAYER_INDEX)) {
						if (temp.getContents(KING_INDEX)) {
							score -= king;
							if (i == 0 || i == 7) {
								score += edge;
							}

							if (j == 0 || j == 7) {
								score += edge;
							}
						} else {
							score -= normal;
							// score -= pos * j * j;
						}
					}

					else if (temp.getContents(OCCUPIED_INDEX)
							&& temp.getContents(PLAYER_INDEX)) {
						if (temp.getContents(KING_INDEX)) {
							score += king;
							if (i == 0 || i == 7) {
								score -= edge;
							}
							if (j == 0 || j == 7) {
								score -= edge;
							}
						} else {
							score += normal;
							// score += pos * (7 - j) * (7 - j);
						}
					}
				}

			}

		}

		return score;
	}

	/**
	 * Initiates the creation of all of the occupiable spaces on the board.
	 */
	private void createBoard() {

		board = new Space[BOARD_LENGTH][BOARD_LENGTH];
		for (int i = 0; i < BOARD_LENGTH; i++) {
			for (int j = (i + 1) % 2; j < BOARD_LENGTH; j += 2) {
				board[i][j] = new Space(i, j);
			}
		}
		for (int i = 0; i < BOARD_LENGTH; i++) {
			for (int j = i % 2; j < BOARD_LENGTH; j += 2) {
				board[i][j] = null;
			}
		}
	}

	/**
	 * Creates the pointers between all of the spaces and their neighbors. Space
	 * B is a neighbor to space A if a piece can move from Space A to Space B
	 * without jumping.
	 */
	private void linkSpaces() {

		for (int i = 0; i < BOARD_LENGTH; i++) {
			for (int j = (i + 1) % 2; j < BOARD_LENGTH; j += 2) {
				if (i > 0 && j > 0) {
					createLink(i, j, i - 1, j - 1);
				}
				if (i > 0 && j < BOARD_LENGTH - 1) {
					createLink(i, j, i - 1, j + 1);
				}
				if (i < BOARD_LENGTH - 1 && j > 0) {
					createLink(i, j, i + 1, j - 1);
				}
				if (i < BOARD_LENGTH - 1 && j < BOARD_LENGTH - 1) {
					createLink(i, j, i + 1, j + 1);
				}
			}
		}
	}

	/**
	 * Creates a pointer in the space at index1 to the space at index2 and
	 * creates a pointer in the space at index2 to the space at index1.
	 * 
	 * @param row1 index1's row.
	 * @param col1 index1's column.
	 * @param row2 index2's row.
	 * @param col2 index2's column.
	 */
	private void createLink(int row1, int col1, int row2, int col2) {
		board[row1][col1].addNeigbor(board[row2][col2]);
		board[row2][col2].addNeigbor(board[row1][col1]);
	}

	/**
	 * Fills the board with both players' pieces in the starting positions.
	 * 
	 */
	private void populateBoard() {

		for (int i = 0; i <= 2; i++) {
			for (int j = (i + 1) % 2; j < BOARD_LENGTH; j += 2) {
				board[i][j].setContents(true, true, false, false);
			}
		}

		for (int i = BOARD_LENGTH - 1; i > BOARD_LENGTH / 2; i--) {
			for (int j = (i + 1) % 2; j < BOARD_LENGTH; j += 2) {
				board[i][j].setContents(true, false, false, false);
			}
		}

		/*
		 * board[4][3].setContents(true, true, true, false);
		 * board[5][4].setContents(true, false, true, false);
		 */
	}

	/**
	 * Returns the current player as a boolean.
	 * 
	 * @return false if player 1's turn. true if player 2's turn.
	 */
	public boolean currentPlayer() {
		return player;
	}

	/**
	 * Signals the game that a position has been selected.
	 * <p>
	 * 
	 * @param row
	 *            Row of the position.
	 * @param col
	 *            Column of the position.
	 * @return If the operation was successful.
	 */
	public boolean select(int row, int col) {

		if (board[row][col] == null) {
			return false;
		}
		if (selected.size() == 0) {
			if (board[row][col].getContents(PLAYER_INDEX) == player
					&& board[row][col].getContents(OCCUPIED_INDEX)) {
				selected.add(board[row][col]);
				board[row][col].setContents(SELECTED_INDEX, true);
				return true;
			}
		} else if (selected.size() == 1) {
			if (selected.getFirst().getContents(KING_INDEX)) {
				if (checkSlide(row, col) || checkJump(row, col)) {
					return true;
				}
			} else if (!player && selected.getFirst().getRow() > row) {
				if (checkSlide(row, col) || checkJump(row, col)) {
					return true;
				}
			} else if (player && selected.getFirst().getRow() < row) {
				if (checkSlide(row, col) || checkJump(row, col)) {
					return true;
				}
			}
		} else if (selected.size() > 1) {
			if (selected.getLast().equals(board[row][col])) {
				move(JUMP);
				return true;
			} else if (!board[row][col].getContents(OCCUPIED_INDEX)
					|| selected.getFirst().equals(board[row][col])) {
				if (selected.getFirst().getContents(KING_INDEX)) {
					return checkJump(row, col);
				} else if (!player && selected.getLast().getRow() > row) {
					return checkJump(row, col);
				} else if (player && selected.getLast().getRow() < row) {
					return checkJump(row, col);
				}
			}
		}
		return false;
	}

	/**
	 * Used by the AI to decide a move without the game enforcing jump checking
	 * <p>
	 * 
	 * @param row
	 * @param col
	 * @return If slide works.
	 */
	public boolean hypSelect(int row, int col) {
		if (board[row][col] == null) {
			return false;
		}
		if (selected.size() == 0) {
			if (board[row][col].getContents(PLAYER_INDEX) == player
					&& board[row][col].getContents(OCCUPIED_INDEX)) {
				selected.add(board[row][col]);
				board[row][col].setContents(SELECTED_INDEX, true);
				return true;
			}
		} else if (selected.size() == 1) {
			if (selected.getFirst().equals(board[row][col])) {
				clearSelected();
				return true;
			} else if (selected.getFirst().getContents(KING_INDEX)) {
				if (hypCheckSlide(row, col)) {
					return true;
				} else if (checkJump(row, col)) {
					return true;
				}
			} else if (!player) {
				if (selected.getFirst().getRow() > row) {
					if (hypCheckSlide(row, col)) {
						return true;
					} else if (checkJump(row, col)) {
						return true;
					}
				}
			} else if (player) {
				if (selected.getFirst().getRow() < row) {
					if (hypCheckSlide(row, col)) {
						return true;

					} else if (checkJump(row, col)) {
						return true;
					}
				}
			}
		} else if (selected.size() > 1) {

			if (selected.getFirst().equals(board[row][col])) {
				clearSelected();
				return true;
			} else if (selected.getLast().equals(board[row][col])) {
				hypMove(true);
				return true;
			} else if (!board[row][col].getContents(OCCUPIED_INDEX)) {
				if (selected.getFirst().getContents(KING_INDEX)) {
					return checkJump(row, col);
				} else if (!player) {
					if (selected.getLast().getRow() > row) {
						return checkJump(row, col);
					}
				} else {
					if (selected.getLast().getRow() < row) {
						return checkJump(row, col);
					}
				}
			}
		}
		return false;
	}

	/**
	 * used by the hypSelect method to execute a move without the game enforcing
	 * jump checking
	 * <p>
	 * 
	 * @param jump
	 */
	private void hypMove(boolean jump) {

		if (!jump) {
			selected.get(1).setContents(selected.get(0).getContents());
			selected.get(1).setContents(SELECTED_INDEX, false);
			selected.get(0).clearContents();
			selected.clear();
		} else {
			while (selected.size() > 0) {
				if (selected.size() == 1) {
					selected.get(0).setContents(SELECTED_INDEX, false);
					selected.clear();
				} else {
					selected.get(2).setContents(selected.get(0).getContents());
					selected.get(0).clearContents();
					selected.get(1).clearContents();
					selected.removeFirst();
					selected.removeFirst();
				}
			}
		}
		checkKing();

		if (player) {
			player = false;
		} else {
			player = true;
		}
	}

	/**
	 * Sub-method for select(). checks if the selected space is valid for a
	 * slide: 1) the selected space is a neighbor of the first space selected 2)
	 * the selected space is not currently occupied
	 * <p>
	 * If the slide is valid, the move is carried out
	 * <p>
	 * 
	 * @param row
	 *            Row of the position.
	 * @param col
	 *            Column of the positon.
	 * @return True if slide is valid.
	 */
	private boolean checkSlide(int row, int col) {
		if (selected.getFirst().isNeighbor(board[row][col])) {
			if (!board[row][col].getContents(OCCUPIED_INDEX)) {
				selected.add(board[row][col]);
				move(false);
				return true;
			}
		}
		return false;
	}

	/**
	 * used by the hypSelect method the check a slide without enforcing jump
	 * checking
	 * <p>
	 * 
	 * @param row
	 * @param col
	 * @return If slide is valid.
	 */
	private boolean hypCheckSlide(int row, int col) {
		if (selected.getFirst().isNeighbor(board[row][col])) {
			if (!board[row][col].getContents(OCCUPIED_INDEX)) {
				selected.add(board[row][col]);
				hypMove(false);
				return true;
			}
		}
		return false;
	}

	/**
	 * Sub-method for select(). checks if the selected space is valid for a
	 * jump: 1) is neighbor of a neighbor to the previously selected piece 2)
	 * the neighbor of the neighbor is diagonal with respect to the previously
	 * selected space 3) the space in between is occupied by an opponent's piece
	 * 4) the selected space is empty
	 * <P>
	 * If the jump is valid, the opponent space and the selected space are added
	 * to the selected list
	 * <p>
	 * 
	 * @param row
	 *            Row of the position.
	 * @param col
	 *            Column of the position.
	 * @return true if the jump is valid.
	 */
	private boolean checkJump(int row, int col) {
		if (!board[row][col].getContents(OCCUPIED_INDEX)) {
			for (int i = 0; i < selected.getLast().numOfNeighbors(); i++) {
				if (selected.getLast().getNeighbor(i)
						.isNeighbor(board[row][col])) {
					if (Math.abs(selected.getLast().getRow()
							- board[row][col].getRow()) == 2) {
						if (Math.abs(selected.getLast().getCol()
								- board[row][col].getCol()) == 2) {
							if (selected.getLast().getNeighbor(i)
									.getContents(OCCUPIED_INDEX)) {
								if (selected.getLast().getNeighbor(i)
										.getContents(PLAYER_INDEX) != player) {
									selected.getLast().getNeighbor(i)
											.setContents(SELECTED_INDEX, true);
									selected.add(selected.getLast()
											.getNeighbor(i));
									selected.add(board[row][col]);
									board[row][col].setContents(SELECTED_INDEX,
											true);
									return true;
								}
							}
						}
					}
				}
			}
		}
		return false;
	}

	/**
	 * Checks if any pieces have reached the opponent's king row and kings them
	 * if needed.
	 */
	private void checkKing() {

		for (int i = 0; i < BOARD_LENGTH; i += 2) {
			if (!board[0][i + 1].getContents(PLAYER_INDEX)
					&& board[0][i + 1].getContents(OCCUPIED_INDEX)) {
				board[0][i + 1].setContents(KING_INDEX, true);
			}
			if (board[BOARD_LENGTH - 1][i].getContents(PLAYER_INDEX)
					&& board[BOARD_LENGTH - 1][i].getContents(OCCUPIED_INDEX)) {
				board[BOARD_LENGTH - 1][i].setContents(KING_INDEX, true);
			}
		}
	}

	/**
	 * Finds if there are slides for the specified position.
	 * 
	 * @param row
	 *            Row of the position.
	 * @param col
	 *            Column of the position.
	 * @return If there is an available slide.
	 */
	private boolean findSlide(int row, int col) {

		Space neighbor;

		if (board[row][col].getContents(OCCUPIED_INDEX)) {
			if (board[row][col].getContents(PLAYER_INDEX) == player) {
				for (int k = 0; k < board[row][col].numOfNeighbors(); k++) {
					neighbor = board[row][col].getNeighbor(k);
					if (!neighbor.getContents(OCCUPIED_INDEX)) {
						if (board[row][col].getContents(KING_INDEX)) {
							return true;
						} else if (board[row][col].getContents(PLAYER_INDEX) == false) {
							if (neighbor.getRow() < row) {
								return true;
							}
						} else {
							if (neighbor.getRow() > row) {
								return true;
							}
						}
					}
				}
			}
		}
		return false;
	}

	/**
	 * Finds if there is an available jump for the position specified.
	 * 
	 * @param row
	 *            Row of the position.
	 * @param col
	 *            Column of the position.
	 * @return If there is an available jump.
	 */
	private boolean findJump(int row, int col) {

		Space neighbor;
		int destRow;
		int destCol;

		if (board[row][col].getContents(OCCUPIED_INDEX)) {
			if (board[row][col].getContents(PLAYER_INDEX) == player) {
				for (int i = 0; i < board[row][col].numOfNeighbors(); i++) {
					neighbor = board[row][col].getNeighbor(i);
					if (neighbor.getContents(OCCUPIED_INDEX)) {
						if (neighbor.getContents(PLAYER_INDEX) != player) {
							destRow = (row - neighbor.getRow() > 0) ? neighbor
									.getRow() - 1 : neighbor.getRow() + 1;
							destCol = (col - neighbor.getCol() > 0) ? neighbor
									.getCol() - 1 : neighbor.getCol() + 1;
							if (destRow >= 0 && destRow < BOARD_LENGTH
									&& destCol >= 0 && destCol < BOARD_LENGTH) {
								if (!board[destRow][destCol]
										.getContents(OCCUPIED_INDEX)) {
									if (board[row][col].getContents(KING_INDEX)) {
										return true;
									} else if (!player) {
										if (destRow < row) {
											return true;
										}
									} else {
										if (destRow > row) {
											return true;
										}
									}
								}
							}
						}
					}
				}
			}
		}
		return false;
	}

	/**
	 * Gets the Spaces for the jumps at the position specified by row and
	 * column.
	 * 
	 * @param row
	 *            The row of the position.
	 * @param col
	 *            The column of the position.
	 * @return The jump Spaces.
	 */
	private LinkedList<Space> getJump(int row, int col) {

		LinkedList<Space> spaces = new LinkedList<Space>();
		Space neighbor;
		int destRow;
		int destCol;

		if (board[row][col].getContents(OCCUPIED_INDEX)) {
			if (board[row][col].getContents(PLAYER_INDEX) == player) {
				for (int i = 0; i < board[row][col].numOfNeighbors(); i++) {
					neighbor = board[row][col].getNeighbor(i);
					if (neighbor.getContents(OCCUPIED_INDEX)) {
						if (neighbor.getContents(PLAYER_INDEX) != player) {
							destRow = (row - neighbor.getRow() > 0) ? neighbor
									.getRow() - 1 : neighbor.getRow() + 1;
							destCol = (col - neighbor.getCol() > 0) ? neighbor
									.getCol() - 1 : neighbor.getCol() + 1;
							if (destRow >= 0 && destRow < BOARD_LENGTH
									&& destCol >= 0 && destCol < BOARD_LENGTH) {
								if (!board[destRow][destCol]
										.getContents(OCCUPIED_INDEX)) {
									if (board[row][col].getContents(KING_INDEX)) {
										spaces.add(neighbor);
										spaces.add(board[destRow][destCol]);
										return spaces;
									} else if (!player) {
										if (destRow < row) {
											spaces.add(neighbor);
											spaces.add(board[destRow][destCol]);
											return spaces;
										}
									} else {
										if (destRow > row) {
											spaces.add(neighbor);
											spaces.add(board[destRow][destCol]);
											return spaces;
										}
									}
								}
							}
						}
					}
				}
			}
		}
		return null;
	}

	/**
	 * Checks the available moves.
	 * 
	 * @return If there is a move to be made.
	 */
	private boolean checkMoves() {
		for (int i = 0; i < BOARD_LENGTH; i++) {
			for (int j = 0; j < BOARD_LENGTH; j++) {
				if (board[i][j] != null) {
					if (findSlide(i, j)) {
						return true;
					} else if (findJump(i, j)) {
						return true;
					}
				}
			}
		}
		return false;
	}

	/**
	 * Returns a copy of the properties of each space of the board
	 * 
	 * @return boolean[][][] representation of the game board.
	 */

	public boolean[][][] getBoard() {

		boolean[][][] boardData = new boolean[BOARD_LENGTH][BOARD_LENGTH][];
		for (int i = 0; i < BOARD_LENGTH; i++) {
			for (int j = 0; j < BOARD_LENGTH; j++) {
				if (board[i][j] != null) {
					boardData[i][j] = board[i][j].getContents();
				} else {
					boardData[i][j] = null;
				}
			}
		}
		// System.out.println(this.toString());
		return boardData;
	}

	/**
	 * Executes a move based on the spaces in the selected list and their order.
	 * <p>
	 * If jump is set to false then move() expects that two spaces are in the
	 * selected list: the source space and the destination space in that order.
	 * <p>
	 * if jump is set to true then move() expects that at least three spaces are
	 * in the selected list: the source space, middle opponent space, and
	 * destination space. in the case of a multi-jump, the list needs an
	 * additional opponent space and an additional destination space in the
	 * selected list.
	 * 
	 * @param jump
	 *            True if single jump. False if multi-jump.
	 */
	private void move(boolean jump) {

		boolean foundJump = false;
		LinkedList<Space> destination;

		if (!jump) {
			for (int i = 0; i < BOARD_LENGTH; i++) {
				for (int j = 0; j < BOARD_LENGTH; j++) {
					if (board[i][j] != null) {
						if ((destination = getJump(i, j)) != null) {
							if (ai && player) {
								clearSelected();
								board[i][j].setContents(SELECTED_INDEX, true);
								destination.getFirst().setContents(
										SELECTED_INDEX, true);
								destination.getLast().setContents(
										SELECTED_INDEX, true);
								selected.add(board[i][j]);
								selected.add(destination.getFirst());
								selected.add(destination.getLast());
								move(JUMP);
								return;
							} else {
								clearSelected();
								board[i][j].setContents(SELECTED_INDEX, true);
								selected.add(board[i][j]);
								return;
							}
						}
					}
				}
			}
			selected.get(1).setContents(selected.get(0).getContents());
			selected.get(1).setContents(SELECTED_INDEX, false);
			selected.get(0).clearContents();
			selected.clear();
		} else {
			while (selected.size() > 1) {
				selected.get(2).setContents(selected.get(0).getContents());
				selected.get(0).clearContents();
				selected.get(1).clearContents();
				selected.removeFirst();
				selected.removeFirst();
			}
			if (selected.size() == 1) {

				if ((destination = getJump(selected.get(0).getRow(), selected
						.get(0).getCol())) == null) {
					selected.get(0).setContents(SELECTED_INDEX, false);
					selected.clear();
				} else {
					if (ai && player) {
						destination.getFirst()
								.setContents(SELECTED_INDEX, true);
						destination.getLast().setContents(SELECTED_INDEX, true);
						selected.add(destination.getFirst());
						selected.add(destination.getLast());
						move(JUMP);
						return;
					}

					foundJump = true;
				}
			}
		}
		if (!foundJump) {
			checkKing();
			if (player) {
				player = false;
			} else {
				player = true;
			}
			if (!checkMoves()) {
				winner = true;
			}
		}
	}

	/**
	 * Sets the currently selected spaces to unselected and clears the selected
	 * list.
	 */
	public void clearSelected() {

		for (int i = 0; i < selected.size(); i++) {
			selected.get(i).setContents(SELECTED_INDEX, false);
		}
		selected.clear();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#equals(java.lang.Object)
	 * 
	 * @author Ilya
	 */
	@Override
	public boolean equals(Object obj) {

		// if object is not a game...its not this game.
		if (!(obj instanceof Game)) {
			return false;
		}

		boolean equal = false;

		// cast object to game so we can access fields.
		Game g = (Game) obj;

		PrintStream sps = System.out;

		// suppress syso printing that happens in .getBoard()
		System.setOut(new PrintStream(new ByteArrayOutputStream()));

		// check if equal
		if (this.player == g.player && this.selected.equals(g.selected)
				&& Arrays.deepEquals(this.getBoard(), g.getBoard())) {

			equal = true;

		}
		// restore printing
		System.setOut(sps);

		return equal;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 * 
	 * Overloaded toString to display a representation of the board with all
	 * relevant data of each space.
	 */
	public String toString() {
		String output = "\n";

		for (int i = 0; i < BOARD_LENGTH; i++) {
			for (int j = 0; j < BOARD_LENGTH; j++) {
				if (board[i][j] != null) {
					output = output.concat(board[i][j].toString());
				} else {
					output = output.concat(" -------- ");
				}
			}
			output = output.concat("\n");
		}

		output = output.concat("\nSize of selected list: " + selected.size()
				+ "\n");

		if (player) {
			output = output.concat("\nPlayer2's Turn\n");
		} else {
			output = output.concat("\nPlayer1's Turn\n");
		}

		return output;
	}

	/**
	 * Finds if the selected space is empty.
	 * 
	 * @return True if the selected Space is empty.
	 */
	public boolean selectedEmpty() {
		if (selected.size() == 0) {
			return true;
		}
		return false;
	}

	/**
	 * Test driver for Game class
	 * 
	 * @param args
	 */
	public static void main(String[] args) {

		Game testGame = new Game(true, 50);
		System.out.println("Score " + testGame.scoreBoard());
		testGame = new Game(testGame);
		System.out.println("Score " + testGame.scoreBoard());
		Scanner keyboard = new Scanner(System.in);
		int row = 0;
		int col = 0;
		boolean end = false;

		System.out.println(testGame.toString());

		if (testGame.currentPlayer()) {
			System.out.println("Player2's Turn");
		} else {
			System.out.println("Player1's Turn");
		}

		System.out.println("Enter row to select followed by enter");
		System.out.println("Then enter the column to select followed by enter");
		System.out.println("Entering any other value for either will quit");

		if ((row = keyboard.nextInt()) == 8 || (col = keyboard.nextInt()) == 8) {
			end = true;
		}

		while (!end) {
			if (testGame.select(row, col)) {
				System.out.println(testGame.toString());

				if (testGame.currentPlayer()) {
					System.out.println("Player2's Turn");
				} else {
					System.out.println("Player1's Turn");
				}

				System.out.println("Enter row to select followed by enter");
				System.out
						.println("Then enter the column to select followed by enter");
				System.out
						.println("Entering any other value for either will quit");

				if ((row = keyboard.nextInt()) < 0 || row > 7
						|| (col = keyboard.nextInt()) > 7 || col < 0) {
					end = true;
				}
			} else {
				System.out.println("No change. Enter row and column again.");
				if ((row = keyboard.nextInt()) == 8
						|| (col = keyboard.nextInt()) == 8) {
					end = true;
				}
			}
		}
	}

	/**
	 * Private class that represents each occupiable space on a board.
	 */
	private class Space implements Serializable {

		/**
		 * 
		 */
		private static final long serialVersionUID = -777330301952299165L;
		LinkedList<Space> neighbor;

		boolean[] contents;
		int row;
		int col;

		/**
		 * Creates a Space defined by its position specified by row and column.
		 * 
		 * @param row
		 *            The row of the Space.
		 * @param col
		 *            The column of the Space.
		 */
		public Space(int row, int col) {
			this.row = row;
			this.col = col;
			neighbor = new LinkedList<Space>();

			contents = new boolean[BITSET_LENGTH];
		}

		/**
		 * Adds a Space as a neighbor of this Space.
		 * 
		 * @param neighbor
		 *            The Space to add as a neighbor of this Space.
		 */
		public void addNeigbor(Space neighbor) {
			this.neighbor.add(neighbor);
		}

		/**
		 * Returns the neighbor at the specified index.
		 * 
		 * @param index
		 *            The index of the intended neighbor.
		 * @return The requested neighbor.
		 */
		public Space getNeighbor(int index) {
			return neighbor.get(index);
		}

		/**
		 * @return The row of the Space.
		 */
		public int getRow() {
			return row;
		}

		/**
		 * @return The column of the Space.
		 */
		public int getCol() {
			return col;
		}

		/**
		 * Returns a boolean value of the Space's specified element.
		 * 
		 * @param index
		 *            Index of 0-3, representing {occupied, player, king,
		 *            selected}.
		 * @return The element value.
		 */
		public boolean getContents(int index) {
			return contents[index];

		}

		/**
		 * Returns a boolean array copy of the Space's elements.
		 * 
		 * @return A boolean array copy of the Space's elements.
		 */
		public boolean[] getContents() {

			boolean[] contentsCopy = new boolean[BITSET_LENGTH];
			for (int i = 0; i < BITSET_LENGTH; i++) {

				contentsCopy[i] = contents[i];
			}
			return contentsCopy;
		}

		/**
		 * Resets all the contents of the Space elements to false.
		 */
		public void clearContents() {
			this.setContents(false, false, false, false);
		}

		/**
		 * Explicitly sets the index specified to the specified value.
		 * 
		 * @param index
		 *            Index of 0-3, representing {occupied, player, king,
		 *            selected}.
		 * @param value
		 *            The value to set the index to.
		 */
		public void setContents(int index, boolean value) {

			contents[index] = value;
		}

		/**
		 * Explicitly set the elements of this Space.
		 * 
		 * @param newData
		 *            Array representing {occupied, player, king, selected}.
		 */
		public void setContents(boolean[] newData) {
			contents = newData;
		}

		/**
		 * Explicitly set the elements of this Space.
		 * 
		 * @param occupied
		 *            If the space is occupied.
		 * @param player
		 *            The player the space belongs to.
		 * @param king
		 *            If the space is a king.
		 * @param selected
		 *            If the space is selected.
		 */
		public void setContents(boolean occupied, boolean player, boolean king,
				boolean selected) {

			contents[OCCUPIED_INDEX] = occupied;
			contents[PLAYER_INDEX] = player;
			contents[KING_INDEX] = king;
			contents[SELECTED_INDEX] = selected;
		}

		/**
		 * @return The number of neighbors this Space has.
		 */
		public int numOfNeighbors() {
			return neighbor.size();
		}

		/**
		 * Checks if a Space is a neighbor of this Space.
		 * 
		 * @param space
		 *            The Space to check.
		 * @return True if Space is a neighbor.
		 */
		public boolean isNeighbor(Space space) {

			for (int i = 0; i < neighbor.size(); i++) {
				if (neighbor.get(i).equals(space)) {
					return true;
				}
			}

			return false;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Object#toString()
		 */
		public String toString() {

			String output = " ";

			output = output.concat("r" + row + "c" + col + " ");

			for (int i = 0; i < BITSET_LENGTH; i++) {

				if (contents[i]) {
					output = output.concat("1");
				} else {
					output = output.concat("0");
				}
			}
			output = output.concat(" ");

			return output;
		}
	}

}